// glib/Spawn.cs : Spawn g_spawn API wrapper
//
// Author: Mike Kestner  <mkestner@novell.com>
//
// Copyright (c) 2007 Novell, Inc.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of version 2 of the Lesser GNU General 
// Public License as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program; if not, write to the
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
// Boston, MA 02111-1307, USA.


namespace GLib {

	using System;
	using System.Runtime.InteropServices;

	public enum SpawnError {
		Fork,
		Read,
		Chdir,
		Acces,
		Perm,
		TooBig,
		NoExec,
		NameTooLong,
		NoEnt,
		NoMem,
		NotDir,
		Loop,
		TxtBusy,
		IO,
		NFile,
		MFile,
		Inval,
		IsDir,
		LibBad,
		Failed,
	}

	[Flags]
	public enum SpawnFlags {
		LeaveDescriptorsOpen = 1 << 0,
		DoNotReapChild = 1 << 1,
		SearchPath = 1 << 2,
		StdoutToDevNull = 1 << 3,
		StderrToDevNull = 1 << 4,
		ChildInheritsStdin = 1 << 5,
		FileAndArgvZero = 1 << 6,
	}

	public delegate void SpawnChildSetupFunc();

	[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
	internal delegate void SpawnChildSetupFuncNative(IntPtr gch);

	internal class SpawnChildSetupWrapper {

		SpawnChildSetupFunc handler;

		public SpawnChildSetupWrapper(SpawnChildSetupFunc handler) {
			if (handler == null)
				return;

			this.handler = handler;
			Data = (IntPtr)GCHandle.Alloc(this);
			NativeCallback = new SpawnChildSetupFuncNative(InvokeHandler);
		}

		public IntPtr Data;
		public SpawnChildSetupFuncNative NativeCallback;

		static void InvokeHandler(IntPtr data) {
			if (data == IntPtr.Zero)
				return;
			GCHandle gch = (GCHandle)data;
			(gch.Target as SpawnChildSetupWrapper).handler();
			gch.Free();
		}
	}

	public class Process {

		public const int IgnorePipe = Int32.MaxValue;
		public const int RequestPipe = 0;

		long pid;

		private Process(int pid) {
			this.pid = pid;
		}

		[DllImport(Global.GLibNativeDll, CallingConvention = CallingConvention.Cdecl)]
		static extern void g_spawn_close_pid(int pid);

		public void Close() {
			g_spawn_close_pid((int)pid);
		}

		[DllImport(Global.GLibNativeDll, CallingConvention = CallingConvention.Cdecl)]
		static extern bool g_spawn_async(IntPtr dir, IntPtr[] argv, IntPtr[] envp, int flags, SpawnChildSetupFuncNative func, IntPtr data, out int pid, out IntPtr error);

		[DllImport(Global.GLibNativeDll)]
		static extern bool g_spawn_async_utf8(IntPtr dir, IntPtr[] argv, IntPtr[] envp, int flags, SpawnChildSetupFuncNative func, IntPtr data, out int pid, out IntPtr error);

		public static bool SpawnAsync(string working_directory, string[] argv, string[] envp, SpawnFlags flags, SpawnChildSetupFunc child_setup, out Process child_process) {
			int pid;
			IntPtr error;
			IntPtr native_dir = Marshaller.StringToPtrGStrdup(working_directory);
			IntPtr[] native_argv = Marshaller.StringArrayToNullTermPointer(argv);
			IntPtr[] native_envp = Marshaller.StringArrayToNullTermPointer(envp);
			SpawnChildSetupWrapper wrapper = new SpawnChildSetupWrapper(child_setup);
			bool result;

			if (Global.IsWindowsPlatform)
				result = g_spawn_async_utf8(native_dir, native_argv, native_envp, (int)flags, wrapper.NativeCallback, wrapper.Data, out pid, out error);
			else
				result = g_spawn_async(native_dir, native_argv, native_envp, (int)flags, wrapper.NativeCallback, wrapper.Data, out pid, out error);

			child_process = new Process(pid);
			Marshaller.Free(native_dir);
			Marshaller.Free(native_argv);
			Marshaller.Free(native_envp);
			if (error != IntPtr.Zero) throw new GLib.GException(error);
			return result;
		}

		[DllImport(Global.GLibNativeDll, CallingConvention = CallingConvention.Cdecl)]
		static extern bool g_spawn_async_with_pipes(IntPtr dir, IntPtr[] argv, IntPtr[] envp, int flags, SpawnChildSetupFuncNative func, IntPtr data, out int pid, IntPtr stdin, IntPtr stdout, IntPtr stderr, out IntPtr error);

		[DllImport(Global.GLibNativeDll)]
		static extern bool g_spawn_async_with_pipes_utf8(IntPtr dir, IntPtr[] argv, IntPtr[] envp, int flags, SpawnChildSetupFuncNative func, IntPtr data, out int pid, IntPtr stdin, IntPtr stdout, IntPtr stderr, out IntPtr error);

		public static bool SpawnAsyncWithPipes(string working_directory, string[] argv, string[] envp, SpawnFlags flags, SpawnChildSetupFunc child_setup, out Process child_process, ref int stdin, ref int stdout, ref int stderr) {
			int pid;
			IntPtr error;
			IntPtr native_dir = Marshaller.StringToPtrGStrdup(working_directory);
			IntPtr[] native_argv = Marshaller.StringArrayToNullTermPointer(argv);
			IntPtr[] native_envp = Marshaller.StringArrayToNullTermPointer(envp);
			SpawnChildSetupWrapper wrapper = new SpawnChildSetupWrapper(child_setup);
			IntPtr in_ptr = stdin == IgnorePipe ? IntPtr.Zero : Marshal.AllocHGlobal(4);
			IntPtr out_ptr = stdout == IgnorePipe ? IntPtr.Zero : Marshal.AllocHGlobal(4);
			IntPtr err_ptr = stderr == IgnorePipe ? IntPtr.Zero : Marshal.AllocHGlobal(4);
			bool result;

			if (Global.IsWindowsPlatform)
				result = g_spawn_async_with_pipes_utf8(native_dir, native_argv, native_envp, (int)flags, wrapper.NativeCallback, wrapper.Data, out pid, in_ptr, out_ptr, err_ptr, out error);
			else
				result = g_spawn_async_with_pipes(native_dir, native_argv, native_envp, (int)flags, wrapper.NativeCallback, wrapper.Data, out pid, in_ptr, out_ptr, err_ptr, out error);

			child_process = new Process(pid);
			if (in_ptr != IntPtr.Zero) {
				stdin = Marshal.ReadInt32(in_ptr);
				Marshal.FreeHGlobal(in_ptr);
			}
			if (out_ptr != IntPtr.Zero) {
				stdout = Marshal.ReadInt32(out_ptr);
				Marshal.FreeHGlobal(out_ptr);
			}
			if (err_ptr != IntPtr.Zero) {
				stderr = Marshal.ReadInt32(err_ptr);
				Marshal.FreeHGlobal(err_ptr);
			}
			Marshaller.Free(native_dir);
			Marshaller.Free(native_argv);
			Marshaller.Free(native_envp);
			if (error != IntPtr.Zero) throw new GLib.GException(error);
			return result;
		}

		[DllImport(Global.GLibNativeDll, CallingConvention = CallingConvention.Cdecl)]
		static extern bool g_spawn_sync(IntPtr dir, IntPtr[] argv, IntPtr[] envp, int flags, SpawnChildSetupFuncNative func, IntPtr data, out IntPtr stdout, out IntPtr stderr, out int exit_status, out IntPtr error);

		[DllImport(Global.GLibNativeDll)]
		static extern bool g_spawn_sync_utf8(IntPtr dir, IntPtr[] argv, IntPtr[] envp, int flags, SpawnChildSetupFuncNative func, IntPtr data, out IntPtr stdout, out IntPtr stderr, out int exit_status, out IntPtr error);

		public static bool SpawnSync(string working_directory, string[] argv, string[] envp, SpawnFlags flags, SpawnChildSetupFunc child_setup, out string stdout, out string stderr, out int exit_status) {
			IntPtr native_stdout, native_stderr, error;
			IntPtr native_dir = Marshaller.StringToPtrGStrdup(working_directory);
			IntPtr[] native_argv = Marshaller.StringArrayToNullTermPointer(argv);
			IntPtr[] native_envp = Marshaller.StringArrayToNullTermPointer(envp);
			SpawnChildSetupWrapper wrapper = new SpawnChildSetupWrapper(child_setup);
			bool result;

			if (Global.IsWindowsPlatform)
				result = g_spawn_sync(native_dir, native_argv, native_envp, (int)flags, wrapper.NativeCallback, wrapper.Data, out native_stdout, out native_stderr, out exit_status, out error);
			else
				result = g_spawn_sync(native_dir, native_argv, native_envp, (int)flags, wrapper.NativeCallback, wrapper.Data, out native_stdout, out native_stderr, out exit_status, out error);

			Marshaller.Free(native_dir);
			Marshaller.Free(native_argv);
			Marshaller.Free(native_envp);
			stdout = Marshaller.PtrToStringGFree(native_stdout);
			stderr = Marshaller.PtrToStringGFree(native_stderr);
			if (error != IntPtr.Zero) throw new GLib.GException(error);
			return result;
		}

		[DllImport(Global.GLibNativeDll, CallingConvention = CallingConvention.Cdecl)]
		static extern bool g_spawn_command_line_async(IntPtr cmdline, out IntPtr error);

		[DllImport(Global.GLibNativeDll)]
		static extern bool g_spawn_command_line_async_utf8(IntPtr cmdline, out IntPtr error);

		public static bool SpawnCommandLineAsync(string command_line) {
			IntPtr error;
			IntPtr native_cmd = Marshaller.StringToPtrGStrdup(command_line);
			bool result;

			if (Global.IsWindowsPlatform)
				result = g_spawn_command_line_async_utf8(native_cmd, out error);
			else
				result = g_spawn_command_line_async(native_cmd, out error);

			Marshaller.Free(native_cmd);
			if (error != IntPtr.Zero) throw new GLib.GException(error);
			return result;
		}

		[DllImport(Global.GLibNativeDll, CallingConvention = CallingConvention.Cdecl)]
		static extern bool g_spawn_command_line_sync(IntPtr cmdline, out IntPtr stdout, out IntPtr stderr, out int exit_status, out IntPtr error);

		[DllImport(Global.GLibNativeDll)]
		static extern bool g_spawn_command_line_sync_utf8(IntPtr cmdline, out IntPtr stdout, out IntPtr stderr, out int exit_status, out IntPtr error);

		public static bool SpawnCommandLineSync(string command_line, out string stdout, out string stderr, out int exit_status) {
			IntPtr error, native_stdout, native_stderr;
			IntPtr native_cmd = Marshaller.StringToPtrGStrdup(command_line);
			bool result;

			if (Global.IsWindowsPlatform)
				result = g_spawn_command_line_sync_utf8(native_cmd, out native_stdout, out native_stderr, out exit_status, out error);
			else
				result = g_spawn_command_line_sync(native_cmd, out native_stdout, out native_stderr, out exit_status, out error);

			Marshaller.Free(native_cmd);
			stdout = Marshaller.PtrToStringGFree(native_stdout);
			stderr = Marshaller.PtrToStringGFree(native_stderr);
			if (error != IntPtr.Zero) throw new GLib.GException(error);
			return result;
		}
	}
}